package com.acme.bankapp.service.bank;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Calendar;

import com.acme.bankapp.domain.bank.Account;
import com.acme.bankapp.domain.bank.Bank;
import com.acme.bankapp.domain.bank.ClientRegistrationListener;
import com.acme.bankapp.domain.bank.SavingsAccount;
import com.acme.bankapp.domain.bank.CheckingAccount;
import com.acme.bankapp.domain.bank.Client;
import com.acme.bankapp.domain.bank.Gender;
import com.acme.bankapp.domain.bank.BankException;
import com.acme.bankapp.service.bank.BankDataLoaderService;
import com.acme.bankapp.service.bank.BankDataLoaderService.FeedLoadException;

public class BankService {
	static String serializeFileName;

	@SuppressWarnings("serial")
	public static class PrintClientListener implements
			ClientRegistrationListener, Serializable {
		public void onClientAdded(Client c) {
			System.out.println("New client " + c.getClientSalutation() + ": "
					+ c.getAccount().maximumAmountToWithdraw());
		}
	}

	@SuppressWarnings("serial")
	public static class EmailNotificationListener implements
			ClientRegistrationListener, Serializable {
		public void onClientAdded(Client c) {
			System.out.println("Notification email for client "
					+ c.getClientSalutation() + " to be sent");
		}
	}

	@SuppressWarnings("serial")
	public static class DebugListener implements ClientRegistrationListener,
			Serializable {
		public void onClientAdded(Client c) {
			Calendar calendar = Calendar.getInstance();
			SimpleDateFormat format = new SimpleDateFormat("HH:mm:ss");
			System.out.println("Client " + c.getClientSalutation() + " "
					+ format.format(calendar.getTime()));
		}
	}

	public enum runCase {
		CASE_DEFAULT, CASE_SERIALIZE, CASE_FEED, CASE_SERVER;
	}

	public static runCase getCase(String[] args) {
		if (args.length == 2) {
			if (args[0].equals("-loadbank")) {
				return runCase.CASE_SERIALIZE;
			} else if (args[0].equals("-feedbank")) {
				return runCase.CASE_FEED;
			}
		} else if (args.length == 3) {
			if (args[2].equals("-server")) {
				return runCase.CASE_SERVER;
			}
		}
		return runCase.CASE_DEFAULT;
	}

	public static Bank serializeBank(String fileName) throws IOException {
		serializeFileName = fileName;
		return readBank(serializeFileName);
	}

	public static Bank loadFromFeedBank(String fileName)
			throws FeedLoadException {
		serializeFileName = "bank.ser";
		return feedBank(fileName);
	}

	public static Bank createEmptyBank() {
		serializeFileName = "bank.ser";
		Bank bank = createBankWithListeners();
		return bank;
	}

	public static Bank createDefaultBank() {
		Bank bank = createEmptyBank();
		generateClients(bank, 7);
		return bank;
	}

	public static Bank createBankWithListeners() {
		ClientRegistrationListener[] listeners = new ClientRegistrationListener[3];
		listeners[0] = new PrintClientListener();
		listeners[1] = new EmailNotificationListener();
		listeners[2] = new DebugListener();
		return new Bank(listeners);
	}

	static void addClient(Bank bank, Client client) {
		if (bank != null)
			bank.addClient(client);
	}

	public static void generateClients(Bank bank, int number) {
		for (int i = 0; i < number; i++) {
			addClient(bank, generateClient());
		}
	}

	public static Client generateClient() {
		String[][] names = {
				{ "Andrey", "Ivan", "Gleb", "Vasiliy", "Oleg", "Stanislav" },
				{ "Diana", "Olga", "Alexandra", "Natalia", "Maria" } };

		Account newAccount;
		try {
			newAccount = Math.random() < 0.5 ? new SavingsAccount(
					Math.random() * 100 - 10) : new CheckingAccount(
					Math.random() * 100 - 10, Math.random() * 100 - 10);
		} catch (IllegalArgumentException e) {
			newAccount = Math.random() < 0.5 ? new SavingsAccount(0)
					: new CheckingAccount(0, 0);
		}
		Gender newGender = Math.random() < 0.5 ? Gender.MALE : Gender.FEMALE;
		String newName = names[newGender.ordinal()][((int) (Math.random() * 100))
				% names[newGender.ordinal()].length];

		return new Client(newName, newGender, newAccount);
	}

	static void modifyBank(Bank bank) throws BankException, IOException {
		if (bank != null) {
			for (int i = 0; i < bank.numClients(); i++) {
				if (Math.random() < 0.5) {
					double amount = Math.random() * 5;
					if (Math.random() < 0.5) {
						bank.getClient(i).getAccount().withdraw(amount);
					} else {
						bank.getClient(i).getAccount().deposit(amount);
					}
				}
			}
			writeBank(bank, serializeFileName);
		}
	}

	static void printMaximumAmountToWithdraw(Bank bank) {
		if (bank != null) {
			System.out.println("List of clients:");
			for (int i = 0; i < bank.numClients(); i++) {
				System.out.println(bank.getClient(i).getClientSalutation()
						+ ": "
						+ bank.getClient(i).getAccount()
								.maximumAmountToWithdraw());
			}
		}
	}

	static void printClients(Bank bank) {
		if (bank != null) {
			System.out.println("List of clients:");
			StringBuffer sbuffer = new StringBuffer();
			for (int i = 0; i < bank.numClients(); i++) {
				sbuffer.append(bank.getClient(i) + "\n");
			}
			System.out.println(sbuffer);
			System.out.println("");
		}
	}

	static Bank feedBank(String fileName)
			throws BankDataLoaderService.FeedLoadException {
		ClientRegistrationListener[] listeners = new ClientRegistrationListener[3];
		listeners[0] = new PrintClientListener();
		listeners[1] = new EmailNotificationListener();
		listeners[2] = new DebugListener();
		Bank bank = new Bank(listeners);
		BankDataLoaderService.feedLoadBank(bank, fileName);
		return bank;
	}

	static void writeBank(Bank bank, String fileName) throws IOException {
		if (bank != null) {
			FileOutputStream fos = null;
			BufferedOutputStream bos = null;
			ObjectOutputStream oos = null;
			try {
				fos = new FileOutputStream(fileName);
				try {
					bos = new BufferedOutputStream(fos);
					try {
						oos = new ObjectOutputStream(bos);
						oos.writeObject(bank);
					} finally {
						if (oos != null)
							oos.close();
					}
				} finally {
					if (bos != null)
						bos.close();
				}
			} catch (IOException e) {
				e.printStackTrace();
			} finally {
				try {
					if (fos != null)
						fos.close();
				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		}
	}

	static Bank readBank(String fileName) {
		Bank newBank = null;
		FileInputStream fis = null;
		BufferedInputStream bis = null;
		ObjectInputStream ois = null;
		try {
			fis = new FileInputStream(fileName);
			try {
				bis = new BufferedInputStream(fis);
				try {
					ois = new ObjectInputStream(bis);
					try {
						newBank = (Bank) ois.readObject();
					} catch (Exception e) {
						e.printStackTrace();
					}
				} finally {
					if (ois != null)
						ois.close();
				}
			} finally {
				if (bis != null)
					bis.close();
			}
		} catch (IOException e) {
			e.printStackTrace();
		} finally {
			try {
				if (fis != null)
					fis.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
		return newBank;
	}

	public static void startServer(Bank bank) {
		BankServer server = new BankServer(bank);
		server.run();
	}
	
}
